package org.lex.ftp.net;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;

import javax.swing.event.EventListenerList;

import org.apache.commons.net.ProtocolCommandEvent;
import org.apache.commons.net.ProtocolCommandListener;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;
import org.lex.filter.Filter;
import org.lex.net.event.ProtocolEvent;
import org.lex.net.event.ProtocolListener;
import org.lex.net.ftp.FTPURL;
import org.lex.net.ftp.IFTPClient;
import org.lex.net.ftp.IFTPFile;

public class ApacheFTPClient implements IFTPClient, ProtocolCommandListener {
	/**
	 * if log debug messages, <br>
	 * true:log debug info. <br>
	 * false:not log debug info
	 */
	private static final boolean DEBUG_LOG = true;

	private FTPClient ftp = null;
	private EventListenerList list = null;

	public ApacheFTPClient() {
		list = new EventListenerList();
		ftp = new FTPClient();
		ftp.addProtocolCommandListener(this);
	}

	public void abort() throws IOException {
		if (false == ftp.abort()) {
			throw new IOException("abort failed");
		}
	}

	/***************************************************************************
	 * Support event system
	 **************************************************************************/
	public void addProtocolListener(ProtocolListener l) {
		list.add(ProtocolListener.class, l);
	}

	public void removeProtocolListener(ProtocolListener l) {
		list.remove(ProtocolListener.class, l);
	}

	public void protocolCommandSent(ProtocolCommandEvent e) {
		ProtocolEvent ce = Adapters.adapt(e);
		Object[] listeners = list.getListenerList();
		for (int i = listeners.length - 2; i >= 0; i -= 2) {
			if (listeners[i] == ProtocolListener.class) {
				((ProtocolListener) listeners[i + 1]).commandSent(ce);
			}
		}
	}

	public void protocolReplyReceived(ProtocolCommandEvent e) {
		ProtocolEvent ce = Adapters.adapt(e);
		Object[] listeners = list.getListenerList();
		for (int i = listeners.length - 2; i >= 0; i -= 2) {
			if (listeners[i] == ProtocolListener.class) {
				((ProtocolListener) listeners[i + 1]).replyReceived(ce);
			}
		}
	}

	/***************************************************************************
	 * RFC 959 commands
	 **************************************************************************/
	public OutputStream append(String file) throws IOException {
		return ftp.appendFileStream(file);
	}

	@Override
	public FTPURL getURL() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public long size(String file) throws IOException {
		throw new UnsupportedOperationException("SIZE command not implements yet");
	}

	public void ascii() throws IOException {
		ftp.setFileType(FTP.ASCII_FILE_TYPE);
	}

	public void binary() throws IOException {
		ftp.setFileType(FTP.BINARY_FILE_TYPE);
	}

	public void cdUp() throws IOException {
		this.maybePending();
		ftp.changeToParentDirectory();
	}

	public void closeServer() throws IOException {
		this.ftp.disconnect();
	}

	public void cwd(String path) throws IOException {
		this.maybePending();
		ftp.changeWorkingDirectory(path);
	}

	public String feature() throws IOException {
		this.maybePending();
		ftp.sendCommand("FEAT");
		return ftp.getReplyString();
	}

	public InputStream get(String file) throws IOException {
		this.maybePending();
		return ftp.retrieveFileStream(file);
	}

	public InputStream get(String file, long offset) throws IOException {
		return null;
	}

	public boolean isServerOpen() {
		if (null == ftp) {
			return false;
		} else if (false == ftp.isConnected()) {
			return false;
		} else {
			int code = ftp.getReplyCode();
			if (FTPReply.isNegativePermanent(code) || FTPReply.isNegativeTransient(code)) {
				return false;
			} else {
				return true;
			}
		}
	}

	public IFTPFile[] list() throws IOException {
		this.maybePending();
		return FTPFileFilter.instance.filter(this.pwd(), this.ftp.listFiles());
	}

	public void login(String user, char[] pass) throws IOException {
		ftp.login(user, new String(pass));
	}

	public void noop() throws IOException {
		this.maybePending();
		ftp.noop();
	}

	public void openServer(String host, int port) throws IOException {
		ftp.connect(host, port);
	}

	public OutputStream put(String file) throws IOException {
		this.maybePending();
		return ftp.storeFileStream(file);
	}

	public String pwd() throws IOException {
		this.maybePending();
		return ftp.printWorkingDirectory();
	}

	public void quit() throws IOException {
		this.maybePending();
		this.ftp.logout();
	}

	public void reInit() throws IOException {
		this.ftp.rein();
	}

	public void rename(String from, String to) throws IOException {
		this.ftp.rename(from, to);
	}

	public void restart(long pos) throws IOException {
		this.ftp.rest(Long.toString(pos));
	}

	public void setPassiveMode(boolean pasv) {
		if (true == pasv)
			ftp.enterLocalPassiveMode();
		else
			ftp.enterLocalActiveMode();
	}

	public void setServerCharset(Charset charset) {
		this.ftp.setControlEncoding(charset.name());
	}

	public String system() throws IOException {
		this.maybePending();
		return ftp.getSystemName();
	}

	private void maybePending() throws IOException {
		if (this.isPending())
			this.ftp.completePendingCommand();
	}

	private boolean isPending() {
		int code = this.ftp.getReplyCode();
		boolean pending = (1 == (code / 100));
		if (pending) {
			debug("pending on " + code);
		} else {
			debug("no pending " + code);
		}
		return pending;
	}

	private static void debug(Object message) {
		if (true == DEBUG_LOG) {
			System.out.print(">>>[debug]: ");
			System.out.println(message);
		}
	}

	private static class FTPFileFilter implements Filter<FTPFile> {
		static final FTPFileFilter instance = new FTPFileFilter();

		/*
		 * filter out "." and ".." file
		 */
		public boolean accept(FTPFile file) {
			if (null == file)
				return false;
			String name = file.getName();
			if (null == name || 0 == name.length())
				return false;
			else if (name.length() < 3) {
				for (int i = 0, n = name.length(); i < n; i++)
					if (name.charAt(i) != '.')
						return true;
				return false;
			}
			return true;
		}

		public IFTPFile[] filter(String pwd, FTPFile[] list) {
			List<IFTPFile> valid = new ArrayList<IFTPFile>(list.length);
			for (FTPFile ftpFile : list) {
				if (true == this.accept(ftpFile)) {
					valid.add(Adapters.adapt(pwd, ftpFile));
				}
			}
			return valid.toArray(new IFTPFile[valid.size()]);
		}
	}
}
